#include "VBO.h"
#include <algorithm>

VBO::~VBO(void)
{
	for(int i = 0; i < vbos.size(); ++i)
	{
		DELETE_PTR(vbos[i]);
	}
}

GLuint VBO::Begin(GLenum mode)
{
	// Check if VBO::Begin() has already been called
	if(bAcceptingVertexData) return 0;
	else bAcceptingVertexData = true;

	VBO_Data *vbo = new VBO_Data;
	vbos.push_back(vbo);
	vbo->mode = mode;

	// Create a new VBO
	glGenBuffers(1, &vbo->id );

	return vbo->id;
}

void VBO::UpdateBegin(GLuint id)
{
	VBO_Data *vbo = NULL;
	for(int i = 0; i < vbos.size(); ++i)
		if(vbos[i]->id == id) { vbo = vbos[i]; break; }

	// Check if VBO::Update() has already been called
	if(bAcceptingVertexData && !vbo) return;
	else bAcceptingVertexData = true;

	vbo->vertices.clear();
	vbos.push_back(vbo);
	vbos.erase( find(vbos.begin(), vbos.end(), vbo) );
}

#define BUFFER_OFFSET(i) ((char *)NULL + (i))

void VBO::End(GLenum usage)
{
	if(!bAcceptingVertexData) return;
	else bAcceptingVertexData = false;

	VBO_Data *vbo = vbos.back();

	// Make the vbo active
	glBindBuffer(GL_ARRAY_BUFFER, vbo->id);

	// Allocate space in video device
	glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * vbo->vertices.size(),NULL, usage);

	// Upload vertex data to video device
	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(Vertex) * vbo->vertices.size(), &vbo->vertices[0]);
	
	if(vbo->hasTexCoord) glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(12));
	if(vbo->hasNormal) glNormalPointer(GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(20));
	if(vbo->hasColor) glColorPointer(4, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(32));
	glVertexPointer(3, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(0));	
}

void VBO::UpdateEnd(GLuint id)
{
	VBO_Data *vbo = NULL;
	for(int i = 0; i < vbos.size(); ++i)
		if(vbos[i]->id == id) { vbo = vbos[i]; break; }

	// Check if VBO::Update() has already been called
	if(bAcceptingVertexData && !vbo) return;
	else bAcceptingVertexData = true;

	// Make the vbo active
	glBindBuffer(GL_ARRAY_BUFFER, vbo->id);

	// Upload vertex data to video device
	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(Vertex) * vbo->vertices.size(), &vbo->vertices[0]);
	
	if(vbo->hasTexCoord) glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(12));
	if(vbo->hasNormal) glNormalPointer(GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(20));
	if(vbo->hasColor) glColorPointer(4, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(32));
	glVertexPointer(3, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(0));	
}

void VBO::Draw(void)
{
	for(int i = 0; i < vbos.size(); ++i)
	{
		VBO_Data *vbo = vbos[i];
		// Make the VBO active
		glBindBuffer(GL_ARRAY_BUFFER, vbo->id);

		// Tell OpenGL what we're gonna draw
		if(vbo->hasTexCoord) glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		if(vbo->hasNormal) glEnableClientState(GL_NORMAL_ARRAY);
		if(vbo->hasColor) glEnableClientState(GL_COLOR_ARRAY);
		glEnableClientState(GL_VERTEX_ARRAY);
	
		if(vbo->hasTexCoord) glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(12));
		if(vbo->hasNormal) glNormalPointer(GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(20));
		if(vbo->hasColor) glColorPointer(4, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(32));
		glVertexPointer(3, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(0));

		// Actually draw the vertices, giving the number of vertices provided
		glDrawArrays(vbo->mode, 0, vbo->vertices.size());

		if(vbo->hasTexCoord) glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		if(vbo->hasNormal) glDisableClientState(GL_NORMAL_ARRAY);
		if(vbo->hasColor) glDisableClientState(GL_COLOR_ARRAY);
		glDisableClientState(GL_VERTEX_ARRAY);
	}
}

#undef BUFFER_OFFSET

void VBO::AddVertex(float x, float y, float z)
{
	if(!bAcceptingVertexData) return;
	else
	{
		Vertex *v = new Vertex;
		v->pos[0] = x;
		v->pos[1] = y;
		v->pos[2] = z;
		vbos.back()->vertices.push_back( *v );
	}
}

void VBO::AddVertex(vec3 v)
{
	if(!bAcceptingVertexData) return;
	else this->AddVertex(v.x, v.y, v.z);
}

void VBO::AddVertex(float *v)
{
	if(!bAcceptingVertexData) return;
	else this->AddVertex(v[0], v[1], v[2]);
}

void VBO::AddNormal(float x, float y, float z)
{
	if(!bAcceptingVertexData) return;
	else
	{
		Vertex *v = &vbos.back()->vertices.back();
		v->normal[0] = x;
		v->normal[1] = y;
		v->normal[2] = z;
		vbos.back()->hasNormal = true;
	}
}

void VBO::AddNormal(vec3 v)
{
	if(!bAcceptingVertexData) return;
	else AddNormal(v.x, v.y, v.z);
}

void VBO::AddNormal(float *v)
{
	if(!bAcceptingVertexData) return;
	else this->AddNormal(v[0], v[1], v[2]);
}

void VBO::AddTexCoord(float u, float v)
{
	if(!bAcceptingVertexData) return;
	else
	{
		Vertex *vert = &vbos.back()->vertices.back();
		vert->texcoord[0] = u;
		vert->texcoord[1] = v;
		vbos.back()->hasTexCoord = true;
	}
}

void VBO::AddTexCoord(vec2 v)
{
	if(!bAcceptingVertexData) return;
	else AddTexCoord(v.x, v.y);
}

void VBO::AddTexCoord(float *v)
{
	if(!bAcceptingVertexData) return;
	else this->AddTexCoord(v[0], v[1]);
}

void VBO::AddColor(float r, float g, float b, float a)
{
	if(!bAcceptingVertexData) return;
	else
	{
		Vertex *v = &vbos.back()->vertices.back();
		v->color[0] = r;
		v->color[1] = g;
		v->color[2] = b;
		v->color[3] = a;
		vbos.back()->hasColor = true;
	}
}

void VBO::AddColor(vec3 v, float a)
{
	if(!bAcceptingVertexData) return;
	else AddColor(v.x, v.y, v.z, a);
}

void VBO::AddColor(float *v)
{
	if(!bAcceptingVertexData) return;
	else this->AddColor(v[0], v[1], v[2], (*(v+3)) ? v[3] : 1.0f);
}

void VBO::SetColor(GLuint id, float r, float g, float b, float a)
{
	VBO_Data *vbo = NULL;
	for(int i = 0; i < vbos.size(); ++i)
		if(vbos[i]->id == id) { vbo = vbos[i]; break; }

	if(!vbo) return;

	for(int i = 0; i < vbo->vertices.size(); ++i)
	{
		Vertex *v = &vbo->vertices[i];
		v->color[0] = r;
		v->color[1] = g;
		v->color[2] = b;
		v->color[3] = a;
	}
	
	vbo->hasColor = true;
}